package org.dmikis.jmmengine.models;

import static java.lang.Math.abs;

enum EquationUnsolvedExceptionType {OUT_OF_LOOP_EXCEPTION;};

/**
 * @author dmikis
 * @version 0.3
 * 
 * Implementation of iterative  algorithm for solving
 * equation represented by <code>Function</code> object. 
 */
public class EquationSolver {
    
    /**
     * Create new instance of <code>EquationSolver</code> for resolving equation f(<i>x</i>)=0.
     * f(<i>x</i>) represented by <code>func</code> object.
     *  
     * @param func function object
     * @param accuracy accuracy of solution. Have to be positive number.
     * @throws IllegalArgumentException if accuracy is negative or zero.
     */
    public EquationSolver(DifferentiableFunction func, double accuracy) throws IllegalArgumentException {
	this.function = func;
	if (accuracy <= 0) {
	    throw new IllegalArgumentException("Accuracy must be positive number!");
	}
	this.accuracy = accuracy;
    }

    /**
     * Get result of solving process.
     * 
     * @return value of the result.
     * @throws EquationUnsolvedException if iterative process keeps continuing more than 50000 cycles.
     */
    public double getResult() throws EquationUnsolvedException {
	/*double x1 = 0.0;
	double x2 = x1 - 100*signum(function.df(x1)) * signum(function.f(x1));
	int loopCount = 0;
	while (abs(function.f(x1)) + abs(function.f(x2)) != abs(function.f(x1) - function.f(x2))) {
	    x1 = x2;
	    x2 = x1 -100*signum(function.df(x1)) * signum(function.f(x1));
	    loopCount++;
	}*/
	double result = 0.0;
	int loopCount = 0;
	while (abs(function.f(result)) > accuracy) {
	    result = result - function.f(result) / function.df(result);
	    if (loopCount++ > MAX_LOOP_COUNT) {
		throw new EquationUnsolvedException(EquationUnsolvedExceptionType.OUT_OF_LOOP_EXCEPTION);
	    }
	}
	return result;
    }

    /**
     * @author dmikis
     * @version 0.1
     * 
     * Exception to be thrown by <code>EquationSolver</code> object
     * if iterative process wasted too much time without any result.
     */
    @SuppressWarnings("serial")
    public static class EquationUnsolvedException extends Exception {

	public EquationUnsolvedException(EquationUnsolvedExceptionType type) {
	    super();
	    this.type = type;
	}

	public EquationUnsolvedExceptionType getType() {
	    return type;
	}

	private EquationUnsolvedExceptionType type;
    }

    protected DifferentiableFunction function;
    protected double accuracy;

    protected static final int MAX_LOOP_COUNT = 100000;
}
